/* 
 * ToDoItem - implementation of ToDoItem, the basic unit of information 
 * 	for the ToDoList program.  The method -adjustPriority implements
 * 	the dynamic priority mechanism.
 *
 * You may freely copy, distribute and reuse the code in this example.
 * This code is provided AS IS without warranty of any kind, expressed 
 * or implied, as to its fitness for any particular use.
 *
 * Copyright 1995 Ralph Zazula (rzazula@next.com).  All Rights Reserved.
 *
 */

#import "ToDoItem.h"
#import "RZ.h"
#import <appkit/appkit.h>
#import <math.h>

typedef struct _toDoItemFlags {
#ifdef __BIG_ENDIAN__
	unsigned int type:8;
	unsigned int private:1;
	unsigned int completed:1;
	unsigned int _RESERVED:22;
#else
	unsigned int _RESERVED:22;
	unsigned int completed:1;
	unsigned int private:1;
	unsigned int type:8;
#endif
} toDoItemFlags;

#define _flags ((toDoItemFlags *)_reservedToDoItem1)

@implementation ToDoItem

+ initialize
{
	if([self class] == [ToDoItem class]) {
		[self setVersion:4];
	}
	return self;
}

- initSubject:(char *)subj startDate:(long)start dueDate:(long)due
	completeDate:(long)completed type:(char)type isPrivate:(BOOL)privateFlag
	isCompleted:(BOOL)completeFlag data:(char *)buf dataLen:(int)len
{
	if(self = [super init]) {

		_flags = (toDoItemFlags *)calloc(1, sizeof(toDoItemFlags));

		[self setSubject:subj];
		[self setStartDate:start];
		[self setDueDate:due];
		[self setType:type];
		[self setPrivate:privateFlag];
		
		/* don't want auto-calc of completion so don't use setCompleted: */
		dateCompleted = completed ? completed : 0;
		_flags->completed = completeFlag;
		
		[self setData:buf];
	}
	return self;
}
	
- initFromItem:(id <ToDoItems>)anItem
{
	char *buf;
	int len;
	
	[anItem getData:&buf length:&len];
	
	return [self initSubject:[anItem subject] startDate:[anItem startDate]
				dueDate:[anItem dueDate] completeDate:[anItem dateCompleted]
				type:[anItem type] isPrivate:[anItem isPrivate]
				isCompleted:[anItem isCompleted] data:buf dataLen:len];
}

- init
{
	return [self initSubject:NULL startDate:time(NULL) dueDate:(time(NULL)+60)
		completeDate:0 type:TODO_TYPE_NORMAL isPrivate:NO isCompleted:NO 
		data:NULL dataLen:0];
}

- free
{
	if(subject) {
		NX_FREE(subject);
		subject = NULL;
	}
	if(data) {
		NX_FREE(data);
		data = NULL;
		dataLen = 0;
	}
	
	free(_flags);
	return [super free];
}

- (char *)subject						{ return subject; }
- (long)priority						{ return priority; }
- (long)startDate						{ return startDate; }
- (long)dueDate						{ return dueDate; }
- (long)dateCompleted				{ return dateCompleted; }
- (char)type							{ return _flags->type; }
- (BOOL)isPrivate						{ return _flags->private; }
- (BOOL)isCompleted					{ return _flags->completed; }

- (const char *)asciiStartDate	
{ 
	static char buf[40];
	
	if(!startDate) {
		return NULL;
	}
	
	strftime(buf, 40, "%m/%d/%y", localtime(&startDate));

	return buf;
}

- (const char *)asciiCompletedDate	
{ 
	static char buf[40];
	
	if(!dateCompleted) {
		return NULL;
	}
	
	strftime(buf, 40, "%m/%d/%y", localtime(&dateCompleted));

	return buf;
}

- (const char *)asciiDueDate		
{ 
	static char buf[40];
	
	switch(_flags->type) {
		case TODO_TYPE_NORMAL :
		case TODO_TYPE_APPOINTMENT :
			strftime(buf, 40, "%m/%d/%y", localtime(&dueDate));
			break;
		case TODO_TYPE_HIGHPRIORITY :
			sprintf(buf, "ASAP!");
			break;
		case TODO_TYPE_LOWPRIORITY :
			sprintf(buf, "LATER");
			break;
	}
	
	return buf;
}

- getData:(char **)d length:(int *)len
{
	*d = data;
	*len = dataLen;
	return self;
}

- setSubject:(const char *)s
{
    subject = RZReplaceStringBuffer(&subject, s);
	 return self;
}

- setPriority:(long)p
{
	priority = p;
   return self;
}

- setStartDate:(long)newDate
{
	startDate = newDate;
   return self;
}

- setDueDate:(long)newDate
{
	dueDate = newDate;
	[self adjustPriority];
   return self;
}

- setType:(char)newType
{
	_flags->type = newType;
	priority = 0;

	return self;
}

- setPrivate:(BOOL)flag
{
	_flags->private = flag;
	return self;
}

- setCompleted:(BOOL)flag
{
	_flags->completed = flag;
	dateCompleted = flag ? time(NULL) : 0;
	return self;
}

- setData:(const char *)d
{
	RZReplaceStringBuffer(&data, d);
	dataLen = data ? strlen(data) + 1 : 0;
   return self;
}

- setDataFromText:aText
{
	NXStream *stream;
	BOOL failed = NO;
	
	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	NX_DURING
	[aText writeRichText:stream];
	NX_HANDLER
	failed = YES;
	NX_ENDHANDLER
	if(!failed) {
		[self setDataFromStream:stream];
		NXCloseMemory(stream, NX_FREEBUFFER);
	}
	
   return self;
}

- setDataFromStream:(NXStream *)stream
{
	char *_data;
	int _dataLen, _maxLen;
	
	NXGetMemoryBuffer(stream, &_data, &_dataLen, &_maxLen);
	[self setData:_data];
	
   return self;
}

- writeToStream:(NXStream *)stream
{
	static id dummyText = nil;
	
	NXPrintf(stream, "Subject: %s\n",[self subject]);
	switch(_flags->type) {
		case TODO_TYPE_NORMAL :
			NXPrintf(stream, "Type: Normal\n");
			break;
			
		case TODO_TYPE_APPOINTMENT :
			NXPrintf(stream, "Type: Appointment\n");
			break;
			
		case TODO_TYPE_LOWPRIORITY : 
			NXPrintf(stream, "Type: Low Priority\n");
			break;
			
		case TODO_TYPE_HIGHPRIORITY :
			NXPrintf(stream, "Type: High Priority\n");
			break;
	}
	NXPrintf(stream, "Created: %s\n",[self asciiStartDate]);
	NXPrintf(stream, "Due on: %s\n",[self asciiDueDate]);
	if(_flags->completed) {
		NXPrintf(stream, "Completed: %s\n",[self asciiCompletedDate]);
	}
	NXPrintf(stream, "\n");
	if(dataLen) {
		NXStream *_stream;
		if(!dummyText) {
			dummyText = [[Text alloc] init];
		}
		_stream = NXOpenMemory(NULL, 0, NX_READWRITE);
		NXWrite(_stream, data, dataLen);
		NXSeek(_stream, 0, NX_FROMSTART);
		[dummyText readRichText:_stream];
		NXCloseMemory(_stream, NX_FREEBUFFER);
		[dummyText writeText:stream];
	}
	return self;
}

#define LOW_PRIORITY_BASE 0
#define LOW_PRIORITY_SIZE 256
#define LOW_PRIORITY_MAX (LOW_PRIORITY_BASE + LOW_PRIORITY_SIZE)
#define NORMAL_PRIORITY_BASE (LOW_PRIORITY_MAX + 1)
#define NORMAL_PRIORITY_SIZE 512
#define NORMAL_PRIORITY_MAX (NORMAL_PRIORITY_BASE + NORMAL_PRIORITY_SIZE)
#define HIGH_PRIORITY_BASE (NORMAL_PRIORITY_MAX + 1)
#define HIGH_PRIORITY_SIZE 256
#define HIGH_PRIORITY_MAX (HIGH_PRIORITY_BASE + HIGH_PRIORITY_SIZE)

#define ONE_DAY (60*60*24)
#define ONE_YEAR (60*60*24*365)
#define OLDEST_ITEM ONE_YEAR
	
- adjustPriority
/* 
 * priority quantizer - I've decided to quantize priorities in the
 * range 0 - 1023 (if you have more than 1K of stuff to do, you're
 * in trouble :-)  The priority space is divided into three regions:
 * 	0 - 255		LOW PRIORITY ITEMS
 *		256 - 767	NORMAL PRIORITY ITEMS
 *		768 - 1023 	HIGH PRIORITY ITEMS
 */
{
	long age = time(NULL) - startDate;
	long deadline = dueDate - time(NULL);
	
	if(_flags->completed) {
		priority = -dateCompleted;
		return self;
	}
		
	switch(_flags->type) {
		case TODO_TYPE_LOWPRIORITY : 
			/* accrue priority over time but never get above LOW_PRIORITY_MAX */
			priority = LOW_PRIORITY_BASE + 
				LOW_PRIORITY_SIZE*((float)age/(float)OLDEST_ITEM);
			priority = MIN(priority, LOW_PRIORITY_MAX);
			break;
			
		case TODO_TYPE_NORMAL :
			/* increase priority as we get nearer the deadline */
			if(deadline > 0) {
				/* quantize the time until the deadline */
				priority = NORMAL_PRIORITY_MAX - 
					NORMAL_PRIORITY_SIZE*((float)deadline/(float)OLDEST_ITEM);
			} else {
				/* past the due date, make max priority */
				priority = NORMAL_PRIORITY_MAX;
			}
			priority = MIN(priority, NORMAL_PRIORITY_MAX);
			break;

		case TODO_TYPE_HIGHPRIORITY :
			/* accrue priority over time but never get above HIGH_PRIORITY_MAX */
			priority = HIGH_PRIORITY_BASE + 
				HIGH_PRIORITY_SIZE*((float)age/(float)OLDEST_ITEM);
			priority = MIN(priority, HIGH_PRIORITY_MAX);
			break;

		case TODO_TYPE_APPOINTMENT :
			/* drop into an appropriate slot based on the appointment date */
			if(deadline > 10*ONE_DAY) {
				priority = LOW_PRIORITY_MAX;
			} else if(deadline < 2*ONE_DAY) {
				priority = HIGH_PRIORITY_MAX;
			} else {
				priority = NORMAL_PRIORITY_MAX;
			}
			break;

	}
		
	return self;
}

- awake
{
	[self adjustPriority];
	return self;
}

- read:(NXTypedStream *)ts
{
	[super read:ts];
	_flags = (toDoItemFlags *)calloc(1,sizeof(toDoItemFlags));
	
	switch(NXTypedStreamClassVersion(ts, [[self class] name])) {
		case 4 :
			NXReadTypes(ts, "*llll*iI",
				&subject, &priority, &startDate, &dueDate, &dateCompleted, 
				&data, &dataLen, _flags);
			break;	
			
		case 3 :
			NXReadTypes(ts, "*llll*ii",
				&subject, &priority, &startDate, &dueDate, &dateCompleted, 
				&data, &dataLen, _flags);
			break;	
			
		case 2 :
			NXReadTypes(ts, "*lll*ii",
				&subject, &priority, &startDate, &dueDate, &data, &dataLen, _flags);
			break;	
			
		case 1 :
			NXReadTypes(ts, "*lll*ic",
				&subject, &priority, &startDate, &dueDate, &data, &dataLen, &_flags->type);
			break;	
			
		case 0 :
			NXReadTypes(ts, "*lll*i",
				&subject, &priority, &startDate, &dueDate, &data, &dataLen);
			break;
	}
	
	return self;
}

- write:(NXTypedStream *)ts
{
	[super write:ts];
	NXWriteTypes(ts, "*llll*iI",
		&subject, &priority, &startDate, &dueDate, &dateCompleted, 
		&data, &dataLen, _flags);
	return self;
}

@end
